package core.domain;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.xml.bind.annotation.XmlRootElement;

import core.domain.Part.PartStatus;
import core.domain.messages.AbstractMessage;

/**
 * @author endre
 */
@XmlRootElement
public class DataFile extends AbstractMessage {

	private Torrent torrent;
	private List<Part> parts = new CopyOnWriteArrayList<Part>(); // thread safe!
	private String filePath;

	/**
	 * use when initializing a new download
	 * 
	 * @param torrent
	 */
	public DataFile(Torrent torrent) {
		this();
		this.torrent = torrent;
		this.filePath = new File(torrent.getFileName()).getAbsolutePath();
		for (int i = 0; i < torrent.getNumberOfPieces(); ++i) {
			Part part = new Part(i);
			part.setParentTorrent(torrent);
			parts.add(part);
		}
	}
	
	private DataFile(){
		super(MessageType.DATAFILE);
	};

	/**
	 * use when creating a new torrent file and its datafile.
	 * 
	 * @param file
	 */
	public DataFile(String stringFile, List<Part> parts) {
		this();
		this.filePath = stringFile;
		this.parts = parts;
		torrent = parts.get(0).getParentTorrent();
	}
	
	public String getFilePath() {
		return filePath;
	}
	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}

	public List<Part> getParts() {
		return parts;
	}

	public void setParts(List<Part> parts) {
		this.parts = parts;
	}

	public Torrent getTorrent() {
		return torrent;
	}
	
	public void setTorrent(Torrent torrent) {
		this.torrent = torrent;
	}

	public boolean add(Part part) {
		PartStatus partStatus = part.getPartStatus();
		if (partStatus.equals(PartStatus.OUT_OF_BOUNDS) || partStatus.equals(PartStatus.MISSING)) {
			return false;
		} else if (part.getPartHash() != Part.calulatePartHash(part.getPayloadRows())) {
			System.out.println("Part: "+ part + " <-- failed Hash check!");
			return false;
		} else if (!part.getParentTorrent().equals(getTorrent())) {
			return false;
		} else if (!parts.contains(part)) {
			System.out.println("Part: " + part + " <-- is successfully added to the data file!");
			part.setPartStatus(PartStatus.OK);
			parts.set(part.getIndex(), part);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * someone asked us to send them the piece at index.
	 * 
	 * @param i
	 * @return
	 */
	public Part getPart(int i) {
		if (i < 0 || torrent.getNumberOfPieces() < i) {
			Part part = new Part(0, null);
			part.setPartStatus(PartStatus.OUT_OF_BOUNDS);
			return part;
		} 
//		else if (parts.get(i).getPartStatus().equals(PartStatus.MISSING)) {
//			//returning the missing part as well... it won't be added on the other side!
//			return parts.get(i);
//		}
		return parts.get(i);
	}

	public boolean isComplete() {
		int actualHash = 0;
		for (Part part : parts) {
			if (part.getPartStatus().equals(PartStatus.MISSING))
				return false;
			actualHash += part.getPartHash();
		}
		if (actualHash != torrent.getFileHash()) {
			return false;
		} else {// should be good
			return true;
		}

	}

	public List<Integer> returnIndexes(List<Part> parts, boolean arrived) {
		List<Integer> result = new ArrayList<Integer>();
		for (Part part : parts) {
			if (part.getPartStatus().equals(PartStatus.MISSING) && !arrived) {
				result.add(part.getIndex());
			} else if (part.getPartStatus().equals(PartStatus.OK) && arrived) {
				result.add(part.getIndex());
			}
		}
		return result;
	}

	public List<Integer> missingPieces() {
		return returnIndexes(parts, false);
	}

	public List<Integer> arrivedPieces() {
		return returnIndexes(parts, true);
	}

	public void writeOut() {
		if (isComplete()) {

			File dataFileOutput = IOUtil.writeDataOut(getParts(), new File(filePath + File.pathSeparator + torrent.getFileName()));
		}
	}

	@Override
	public String toString() {
		return "DataFile [torrent=" + torrent + ", filePath=" + filePath + "]";
	}
	
	
	
}
